﻿using GitHubCopilotProxies.Handlers;

using Microsoft.Extensions.FileProviders;

using Serilog;
using Serilog.Events;

using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Text.Unicode;

namespace GitHubCopilotProxies;

public class Program
{
    public static void Main(string[] args)
    {
        if (File.Exists(".env"))
        {
            var lines = File.ReadAllLines(".env")
               .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"));
            foreach (var line in lines)
            {
                var parts = line.Split('=', 2);
                if (parts.Length == 2)
                    Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Split('#').First().Trim());
            }
        }

        var builder = WebApplication.CreateSlimBuilder(args);
        var urls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS")?.Split(';').ToHashSet() ?? new HashSet<string>();
        urls.Add("https://[::]:443");
        builder.WebHost.UseUrls(urls.ToArray());
        builder.WebHost.UseKestrelHttpsConfiguration();
        builder.WebHost.ConfigureKestrel(serverOptions =>
        {
            X509Certificate2? cert = null;
            async Task DownloadCertFilesAsync()
            {
                using var httpClient = new HttpClient() { BaseAddress = new Uri("https://data-1251486259.cos.ap-beijing.myqcloud.com/copilot-ssl/") };
                async Task DownloadCertFileAsync(string filename)
                {
                    var fileBytes = await httpClient.GetByteArrayAsync(filename);
                    await File.WriteAllBytesAsync($"cert/{filename}", fileBytes);
                }
                Directory.CreateDirectory("cert");
                await DownloadCertFileAsync("ssl.pem");
                await DownloadCertFileAsync("ssl.key");
            }
            const string pemPath = "cert/ssl.pem";
            const string keyPath = "cert/ssl.key";
            try
            {
                void DownloadCert()
                {
                    DownloadCertFilesAsync().GetAwaiter().GetResult();
                    cert = X509Certificate2.CreateFromPemFile(pemPath, keyPath);
                }
                if (File.Exists(pemPath) && File.Exists(keyPath))
                {
                    cert = X509Certificate2.CreateFromPemFile(pemPath, keyPath);
                    if ((cert.NotAfter - DateTime.Today).TotalDays < 30)
                        DownloadCert();
                }
                else DownloadCert();
            }
            catch { }
            if (cert is not null)
            {
                cert = X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pkcs12), null);
                serverOptions.ConfigureHttpsDefaults(httpsOptions => httpsOptions.ServerCertificate = cert);
            }
        });

        var configuration = builder.Configuration;
        builder.Host.UseSerilog((context, configuration) =>
        {
            var isDev = context.HostingEnvironment.IsDevelopment();
            //configuration.MinimumLevel.Verbose();
            //configuration.Enrich.FromLogContext();
            configuration.MinimumLevel.Override("Microsoft", LogEventLevel.Information);
            configuration.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning);
            configuration.MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", isDev ? LogEventLevel.Information : LogEventLevel.Warning);
            configuration.MinimumLevel.Override("System.Net.Http.HttpClient", isDev ? LogEventLevel.Debug : LogEventLevel.Error);
            configuration.WriteTo.Console(restrictedToMinimumLevel: isDev ? LogEventLevel.Verbose : LogEventLevel.Information);
            configuration.WriteTo.File(
                Path.Combine(context.HostingEnvironment.ContentRootPath, "logs", @".log"),
                restrictedToMinimumLevel: isDev ? LogEventLevel.Information : LogEventLevel.Warning,
                rollingInterval: RollingInterval.Day,
                retainedFileCountLimit: 7,
                shared: true
            );
        });

        builder.Services.AddHttpClient();
        builder.Services.AddMemoryCache();
        builder.Services.AddHttpContextAccessor();
        builder.Services.ConfigureHttpJsonOptions(options =>
        {
            options.SerializerOptions.Encoder = AppJsonSerializerContext.Web.Options.Encoder;
            options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Web);
        });
        builder.Services.AddSingleton(new EmbeddedFileProvider(Assembly.GetEntryAssembly()!));

        var app = builder.Build();
        app.UseSerilogRequestLogging();

        app.MapPost("/login/device/code", AuthGitHub.PostLoginDeviceCodeAsync);
        app.MapGet("/login/device", AuthGitHub.UserLoginDeviceAsync);
        app.MapGet("/login/oauth/authorize", AuthGitHub.GetLoginOauthAuthorizeAsync);
        app.MapPost("/login/oauth/access_token", AuthGitHub.GetLoginOauthAccessTokenAsync);
        app.MapGet("/site/sha", AuthGitHub.GetSiteShaAsync);

        app.MapGet("/user", User.GetLoginUser);
        app.MapGet("/user/orgs", User.GetUserOrgs);
        app.MapGet("/api/v3/user", User.GetLoginUser);
        app.MapGet("/api/v3/user/orgs", User.GetUserOrgs);
        app.MapGet("/teams/{teamID}/memberships/{username}", User.GetMembership);
        app.MapGet("/copilot_internal/user", User.GetCopilotInternalUser);
        app.MapGet("/copilot_internal/v2/token", User.GetDisguiseCopilotInternalV2Token);

        app.MapPost("/v1/engines/copilot-codex/completions", CopilotCompletions.CodeCompletions);
        app.MapPost("/v1/engines/copilot-codex", CopilotCompletions.CodeCompletions);
        app.MapPost("/chat/completions", CopilotCompletions.ChatCompletions);
        app.MapPost("/v1/chat/completions", CopilotCompletions.ChatCompletions);
        app.MapPost("/v1/engines/copilot-centralus-h100/speculation", CopilotCompletions.CodeCompletions);
        app.MapPost("/chunks", CopilotCompletions.HandleChunks);
        app.MapPost("/embeddings", CopilotCompletions.HandleEmbeddings);

        app.MapGet("/telemetry", () => string.Empty);
        app.MapPost("/telemetry", Telemetry.PostTelemetryAsync);

        app.MapGet("/help", Utils.Help);
        app.MapGet("/models", Utils.GetModels);
        app.MapGet("/_ping", Utils.GetPing);
        app.MapGet("/agents", Utils.GetAgents);
        app.MapGet("/api/v3/meta", Utils.GetV3meta);
        app.MapGet("/api/v3/", Utils.GetCliv3);
        app.MapGet("/", Utils.GetCliv3);

        app.Run();
    }
}

